/*
 * Copyright 2007 National Bioinformatics Network
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package dgbgui.gui.widgets.layouts;

import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;

import org.jgrapht.DirectedGraph;
import org.jgrapht.event.ConnectedComponentTraversalEvent;
import org.jgrapht.event.EdgeTraversalEvent;
import org.jgrapht.event.TraversalListener;
import org.jgrapht.event.VertexTraversalEvent;
import org.jgrapht.traverse.BreadthFirstIterator;

import dgbgui.gui.widgets.HasEdgePort;
import dgbgui.gui.widgets.Widget;
import dgbgui.gui.widgets.visual.EdgeWidget;
import dgbgui.modeldocument.primitives.DefaultEdgeWithWidget;
import edu.umd.cs.piccolo.PNode;

@SuppressWarnings("serial")
public class HierarchicalTreeLayout extends GraphLayout {

    
    private class TraversalDepthConstructor<V,E> implements TraversalListener<V,E> {
        
        public TraversalDepthConstructor(DirectedGraph<V,E> graph) {
            m_graph = graph;
            
            // Initialise the depth map with 0
            for ( V v : graph.vertexSet() ) {
                m_depthMap.put(v,0);
            }
        }
        
        public int getDepth(V v) {
            return m_depthMap.get(v);
        }
        
        public void vertexFinished(VertexTraversalEvent<V> v) {};
        public void vertexTraversed(VertexTraversalEvent<V> v) {};
        public void connectedComponentFinished(ConnectedComponentTraversalEvent e) {}
        public void connectedComponentStarted(ConnectedComponentTraversalEvent e) {}
        
        public void edgeTraversed(EdgeTraversalEvent<V,E> e) {
//            System.out.println("edgeTraversed:" 
//                    + m_graph.getEdgeSource(e.getEdge()).toString() + " -> " 
//                    + m_graph.getEdgeTarget(e.getEdge()).toString());
            
            // Depth is the source vertex's depth + 1
            int depth = m_depthMap.get(m_graph.getEdgeSource(e.getEdge())) + 1;
            m_depthMap.put(m_graph.getEdgeTarget(e.getEdge()),depth);
            
        }
        
        // The graph being traversed
        DirectedGraph<V,E> m_graph;
        
        // The depth map
        Map<V,Integer> m_depthMap = new HashMap<V,Integer>();
        

    }

    public HierarchicalTreeLayout(PNode parent, double margin, double spacing) {
        super(parent,margin,spacing); 
    }


    public void setGraph(DirectedGraph<Widget,DefaultEdgeWithWidget> graph) {
        m_widgetGraph = graph;
        
        //  Naive algorithm: Put vertices of the same depth on the same level,
        // starting at the bottom level.
        BreadthFirstIterator<Widget,DefaultEdgeWithWidget>iter = 
            new BreadthFirstIterator<Widget,DefaultEdgeWithWidget>(m_widgetGraph);
        
        TraversalDepthConstructor<Widget,DefaultEdgeWithWidget>depthConstructor 
            = new TraversalDepthConstructor<Widget,DefaultEdgeWithWidget>(m_widgetGraph);
        iter.addTraversalListener(depthConstructor);
        
        // Do the breadth-first search to construct the depth map
        while ( iter.hasNext() ) {
            iter.next();
        }
        
        Map<Integer,Vector<Widget>> depthMap = new HashMap<Integer,Vector<Widget>>();
        for ( Widget w : m_widgetGraph.vertexSet() ) {
            int depth = depthConstructor.getDepth(w);
            if ( !depthMap.containsKey(depth) ) {
                depthMap.put(depth, new Vector<Widget>());
            }
            depthMap.get(depth).add(w);
            //System.out.println("Next: " + w.toString() + " Depth: " + depthConstructor.getDepth(w));
        }
        
        // Do the layout according the to depth
        if ( depthMap.size() > 0 ) {
            double yOffset = m_margin;
            for ( int k = 0; k <= Collections.max(depthMap.keySet()); ++k) {

                double xPadding = m_spacing;

                double totalWidth = xPadding;
                for ( Widget w : depthMap.get(k) ) {
                    totalWidth += w.getFullBounds().getWidth() + xPadding; 
                }

                // Put the layer in the middle
                double xOffset =  -totalWidth/2;
                double maxHeight = 0;
                for ( Widget w : depthMap.get(k) ) {
                    w.setOffset(xOffset,yOffset);
                    xOffset += w.getFullBounds().getWidth() + xPadding;
                    maxHeight = Math.max(maxHeight, w.getFullBounds().getHeight());
                }
                
                yOffset += maxHeight + m_spacing;
            }

            // Update the edges
            for ( DefaultEdgeWithWidget edgeWidget : m_widgetGraph.edgeSet() ) {
                Widget sourceWidget = m_widgetGraph.getEdgeSource(edgeWidget);
                Widget targetWidget = m_widgetGraph.getEdgeTarget(edgeWidget);

                Line2D centerLine = new Line2D.Double(
                        sourceWidget.getFullBounds().getCenter2D(),
                        targetWidget.getFullBounds().getCenter2D());

                if ( ( sourceWidget instanceof HasEdgePort ) && 
                        ( targetWidget instanceof HasEdgePort ) ) { 
                    edgeWidget.getWidget().setLine(new Line2D.Double(
                            ((HasEdgePort)sourceWidget).getPort(centerLine),
                            ((HasEdgePort)targetWidget).getPort(centerLine)));
                }

            }
        }
        
    }
    
    public void layoutChildren() {
        
        // Move all the children to the top left corner
        double minXOffset = Double.MAX_VALUE;
        double minYOffset = Double.MAX_VALUE;
        
        // Calculate the minimum offset
        for ( int index = 0; index < getChildrenCount(); ++index ) {
            PNode child = getChild(index);
            if (!( child instanceof EdgeWidget )) {
                minXOffset = Math.min(child.getXOffset(), minXOffset);
                minYOffset = Math.min(child.getYOffset(), minYOffset);
            }
        }
        
        // Move to the upper left corner
        for ( int index = 0; index < getChildrenCount(); ++index ) {
            PNode child = getChild(index);
            Point2D currentOffset = child.getOffset();
            child.setOffset(new Point2D.Double(
                    currentOffset.getX() - minXOffset + m_margin,
                    currentOffset.getY() - minYOffset + m_margin));
        }
        
    }


}
